﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Drawing;
using System.IO;
using System.Drawing.Imaging;
using System.Xml;
using System.Threading;
using System.Net.Sockets;
using System.Diagnostics;
using System.ComponentModel;
using System.Drawing.Drawing2D;

namespace AirLib
{
    /// <summary>
    /// This Contains commands to Control the specified Apple TV
    /// </summary>
    public class AppleTv
    {
        #region Constants

        public const int AppleTvPort = 7000;
        public const int ScreenInfoPort = 7100;

        #endregion Constants

        #region Fields

        private Guid _sessionGuid = Guid.NewGuid();
        private bool _openVideo = false;
        private string _appleTvIpAddress;
        private AirLib.VideoListener _videoListener;
        List<string> _photoTransitions = new List<string>();
        Dictionary<string, string> _screenInfo = new Dictionary<string, string>();
        Thread _videoThread;

       
        #endregion Fields

        #region Delegates

        public delegate void EventFiredDelegate(string eventMessage);
        public event EventFiredDelegate EventRaised;

        #endregion Delegates


        /// <summary>
        /// Initializes a new instance of the <see cref="AppleTv"/> class.
        /// </summary>
        /// <param name="ipAddress">The ip address.</param>
        public AppleTv(string ipAddress, int streamHostPort)
        {

            System.Net.ServicePointManager.Expect100Continue = false;
            _appleTvIpAddress = ipAddress;
            _videoListener = new AirLib.VideoListener(streamHostPort);            

            //Not currently working
            //Thread eventThread = new Thread(new ThreadStart(StartRecievingEvents));
            //eventThread.Start();
        }

        /// <summary>
        /// Gets the apple TV URL.
        /// </summary>
        /// <returns></returns>
        private Uri GetAppleTvUrl()
        {
            return new Uri("http://" + _appleTvIpAddress + ":" + AppleTvPort + "/");
        }

       

        /// <summary>
        /// Starts recieving events from the AppleTV.
        /// </summary>
        private void StartRecievingEvents()
        {
            //This is supposed to be a "reverse" connection and while the connection appears to be set up,
            //I have not noted any events firing.  It is quite possible that this is simply not done correctly.

            string request = "POST /reverse HTTP/1.1\n" +
                         "Upgrade: PTTH/1.0\n" +
                         "Host: 192.168.1.11:83\n" +
                         "Connection: Upgrade\n" +
                         "Content-Length: 0\n" +
                         "User-Agent: MediaControl/1.0\n" +
                         "X-Apple-Purpose: event\n" +
                         "X-Apple-Session-ID: " + _sessionGuid.ToString() + "\n\n";


            using (TcpClient tcpClient = new TcpClient(_appleTvIpAddress, AppleTvPort))
            {

                //get the client stream to read data from.
                NetworkStream clientStream = tcpClient.GetStream();

                byte[] byData = System.Text.Encoding.ASCII.GetBytes(request);
                clientStream.Write(byData, 0, byData.Length);

                byte[] buffer = new byte[10000];
                
                int recieved = clientStream.Read(buffer, 0, buffer.Length);
                string str = Encoding.ASCII.GetString(buffer, 0, recieved);
                if (EventRaised != null)
                {
                    EventRaised(str);
                }
            }
        }
        
        /// <summary>
        /// Gets the available photo transitions.
        /// </summary>
        /// <returns>Available Photo Transitions</returns>
        public List<string> GetAvailablePhotoTransitions()
        {
            if (_photoTransitions.Count == 0)
            {
                HttpWebRequest request = WebRequest.Create(GetAppleTvUrl() + "slideshow-features") as HttpWebRequest;
                request.UserAgent = "MediaControl/1.0";
                request.Headers.Add("X-Apple-Session-ID", _sessionGuid.ToString());
                request.Method = "GET";
                var responseStream = request.GetResponse().GetResponseStream();

                // used to build entire input
                StringBuilder sb = new StringBuilder();
                // used on each read operation
                byte[] buf = new byte[8192];

                int count = 0;
                do
                {
                    // fill the buffer with data
                    count = responseStream.Read(buf, 0, buf.Length);
                    // make sure we read some data
                    if (count != 0)
                    {
                        // translate from bytes to ASCII text
                        sb.Append(Encoding.ASCII.GetString(buf, 0, count));
                    }
                }
                while (count > 0); // any more data to read?

                var plist = new PList(sb.ToString());

                foreach (System.Collections.Generic.Dictionary<string,dynamic> item in plist.Values.First())
                {
                    _photoTransitions.Add(item.Values.First());
                }

            }

            return _photoTransitions;
        }

        /// <summary>
        /// Gets the Screen Info
        /// </summary>
        /// <returns>Screen Information</returns>
        public Dictionary<string,string> GetScreenInfo()
        {
            if (_screenInfo.Count == 0)
            {
                //This request must be on port 7100
                HttpWebRequest request = WebRequest.Create(GetAppleTvUrl().ToString().Replace(AppleTvPort.ToString(),ScreenInfoPort.ToString()) + "stream.xml") as HttpWebRequest;
                request.Method = "GET";
                var responseStream = request.GetResponse().GetResponseStream();

                // used to build entire input
                StringBuilder sb = new StringBuilder();
                // used on each read operation
                byte[] buf = new byte[8192];

                int count = 0;
                do
                {
                    // fill the buffer with data
                    count = responseStream.Read(buf, 0, buf.Length);
                    // make sure we read some data
                    if (count != 0)
                    {
                        // translate from bytes to ASCII text
                        sb.Append(Encoding.ASCII.GetString(buf, 0, count));
                    }
                }
                while (count > 0); // any more data to read?

                var plist = new PList(sb.ToString());

                foreach (string key in plist.Keys)
                {
                    _screenInfo.Add(key, plist[key].ToString());
                }

            }

            return _screenInfo;
        }




        /// <summary>
        /// Shows the photo.
        /// </summary>
        /// <param name="path">The path.</param>
        /// <param name="transition">The transition.</param>
        public void ShowPhoto(string path)
        {
            ShowPhoto((Bitmap)Bitmap.FromFile(path));            
        }

        /// <summary>
        /// Shows the photo.
        /// </summary>
        /// <param name="photo">The photo.</param>
        public void ShowPhoto(Bitmap photo)
        {
            
            //Resize to match screen
            int MaxHeight = int.Parse(GetScreenInfo()["height"]);
            int MaxWidth = int.Parse(GetScreenInfo()["width"]);
            
            if (photo.Width <= MaxWidth)
            {
                MaxWidth = photo.Width;
            }

            int NewHeight = photo.Height * MaxWidth / photo.Width;
            if (NewHeight > MaxHeight)
            {
                // Resize with height instead
                MaxWidth = photo.Width * MaxHeight / photo.Height;
                NewHeight = MaxHeight;
            }

            Image newImage = new Bitmap(MaxWidth, NewHeight);
            using (Graphics graphicsHandle = Graphics.FromImage(newImage))
            {
                graphicsHandle.InterpolationMode = InterpolationMode.HighQualityBicubic;
                graphicsHandle.DrawImage(photo, 0, 0, MaxWidth, NewHeight);
            }

            foreach (PropertyItem item in photo.PropertyItems)
            {
                newImage.SetPropertyItem(item);
            }

            // Clear handle to original file so that we can overwrite it if necessary
            photo.Dispose();

            byte[] byteArray = new byte[0];
            using (MemoryStream stream = new MemoryStream())
            {
                newImage.Save(stream, ImageFormat.Jpeg);
                stream.Close();

                byteArray = stream.ToArray();
            }

            ShowPhoto(byteArray);
        }

        /// <summary>
        /// Shows the photo.
        /// </summary>
        /// <param name="photo">The photo.</param>
        public void ShowPhoto(byte[] photo)
        {
            //if (!transition.Contains(transition))
            //{
            //    throw new ArgumentException("AppleTV does not support transition: " + transition);
            //}

            StopVideo();
            var webClient = new WebClient();
            //webClient.Headers.Add("X-Apple-Transition", transition);
            webClient.Headers.Add("User-Agent", "MediaControl/1.0");
            webClient.Headers.Add("X-Apple-Session-ID", _sessionGuid.ToString());
            webClient.UploadData(GetAppleTvUrl() + "photo", photo);            
        }

        /// <summary>
        /// Caches the photo.
        /// </summary>
        /// <param name="path">The path.</param>
        /// <returns>The cached photo's Id</returns>
        public Guid CachePhoto(string path)
        {
            byte[] photo = System.IO.File.ReadAllBytes(path);
            return CachePhoto(photo);
        }

        /// <summary>
        /// Caches the photo.
        /// </summary>
        /// <param name="photo">The photo.</param>
        /// <returns>The cached photo's Id</returns>
        public Guid CachePhoto(Bitmap photo)
        {
            byte[] byteArray = new byte[0];
            using (MemoryStream stream = new MemoryStream())
            {
                photo.Save(stream, ImageFormat.Jpeg);
                stream.Close();

                byteArray = stream.ToArray();
            }

            return CachePhoto(byteArray);
        }

        /// <summary>
        /// Cache the photo.
        /// </summary>
        /// <param name="photo">The photo.</param>
        /// <returns>The cached photo's Id</returns>
        public Guid CachePhoto(byte[] photo)
        {
            var webClient = new WebClient();
            webClient.Headers.Add("User-Agent", "MediaControl/1.0");
            webClient.Headers.Add("X-Apple-Session-ID", _sessionGuid.ToString());
            webClient.Headers.Add("X-Apple-AssetAction", "cacheOnly");
            Guid photoGuid = Guid.NewGuid();
            webClient.Headers.Add("X-Apple-AssetKey", photoGuid.ToString());
            webClient.UploadData(GetAppleTvUrl() + "photo", photo);
            
            return photoGuid;
        }

        /// <summary>
        /// Shows the cached photo.
        /// </summary>
        /// <param name="photoGuid">The photo GUID.</param>
        /// <param name="transition">The transition.</param>
        public void ShowCachedPhoto(Guid photoGuid)
        {
            
            StopVideo();
            var webClient = new WebClient();
            webClient.Headers.Add("User-Agent", "MediaControl/1.0");
            webClient.Headers.Add("X-Apple-AssetAction", "displayCached");
            webClient.Headers.Add("X-Apple-Session-ID", _sessionGuid.ToString());
            webClient.Headers.Add("X-Apple-AssetKey", photoGuid.ToString());
            webClient.UploadString(GetAppleTvUrl() + "photo", string.Empty);
        }

        /// <summary>
        /// Starts a video.
        /// </summary>
        /// <param name="url">The URL of the video to play.</param>
        /// <param name="startPosition">The start position of the video. This value must be between 0 and 1</param>
        public void StartVideo(string fileName, float startPosition = 0)
        {
            if (startPosition > 1)
            {
                throw new ArgumentException("Start Position must be between 0 and 1");
            }

            FileInfo fileInfo = new FileInfo(fileName);
            _videoListener.CurrentFile = fileName;
            _videoThread = new Thread(new ParameterizedThreadStart(StartVideoAsync));
            _videoThread.Start(startPosition);
        }

        private void StartVideoAsync(object obj)
        {
            float startPosition = (float)obj;
            FileInfo fileInfo = new FileInfo(_videoListener.CurrentFile);
            string url = "http://" + Dns.GetHostAddresses(Dns.GetHostName()).First(i => i.AddressFamily == AddressFamily.InterNetwork).ToString() + ":" + _videoListener.Port + "/" + fileInfo.Name;
            
            TcpClient tcpClient = new TcpClient(_appleTvIpAddress, AppleTvPort);
            tcpClient.ReceiveTimeout = 100000;
            tcpClient.SendTimeout = 100000;

            //get the client stream to read data from.
            NetworkStream clientStream = tcpClient.GetStream();

            string body =
           "Content-Location: " + url + "\n" +
           "Start-Position: " + startPosition + "\n";

            string request = "POST /play HTTP/1.1\n" +
            "User-Agent: MediaControl/1.0\n" +
            "Content-Type: text/parameters\n" +
            "Content-Length: " + Encoding.ASCII.GetBytes(body).Length + "\n" +
            "X-Apple-Session-ID:" + _sessionGuid.ToString() + "\n\n";

            //Send the headers
            SendMessage(clientStream, request);
            //Send the body
            SendMessage(clientStream, body);

            //Get the response
            byte[] myReadBuffer = new byte[1024];
            StringBuilder myCompleteMessage = new StringBuilder();
            int numberOfBytesRead = 0;
            numberOfBytesRead = clientStream.Read(myReadBuffer, 0, myReadBuffer.Length);
            myCompleteMessage.Append(Encoding.ASCII.GetString(myReadBuffer, 0, numberOfBytesRead));

            _openVideo = true;

            //Now start doing a "keepalive"
            while (_openVideo)
            {
                //Simply send the characters "ok" every two seconds
                //Todo: Determine what the actual message that apple products send
                SendMessage(clientStream, "ok");
                Thread.Sleep(100);
            }
            clientStream.Close();
            tcpClient.Close();
        }



        /// <summary>
        /// Sends a message across the NetworkStream
        /// </summary>
        /// <param name="clientStream">The stream to send the message down</param>
        /// <param name="message">The message to send</param>
        public void SendMessage(NetworkStream clientStream, string message)
        {
            byte[] buffer = new ASCIIEncoding().GetBytes(message);
            try
            {
                clientStream.Write(buffer, 0, buffer.Length);
                clientStream.Flush();
            }
            catch (System.IO.IOException e)
            {
                Debug.WriteLine("IOException: " + e.Message);
            }
        }

        

        /// <summary>
        /// Gets the response callback.
        /// </summary>
        /// <param name="asynchronousResult">The asynchronous result.</param>
        private void GetResponseCallback(IAsyncResult asynchronousResult)
        {
            var request = (HttpWebRequest)asynchronousResult.AsyncState;
            var response = (HttpWebResponse)request.EndGetResponse(asynchronousResult);
            StreamReader streamReader = new StreamReader(response.GetResponseStream());
            string result = streamReader.ReadToEnd();


            Console.Write(result);

        }

        /// <summary>
        /// Stops the video.
        /// </summary>
        public void StopVideo()
        {
            if (_openVideo)
            {
                _openVideo = false;
                _videoThread.Join();
            }
        }


    }
}
